﻿using System;

namespace Q.Extensions
{
    /// <summary>  
    /// Extensions help with handling enumeration with flags.  
    /// </summary>  
    public static class EnumFlagsExtensions
    {
        /// <summary>  
        /// Test if the flag is set to this value.  
        /// </summary>  
        public static Boolean IsSet(this Enum enumValue,
            Enum valueToTest)
        {
            if (enumValue.GetType() != valueToTest.GetType())
                throw new ArgumentException("valueToTest",
                    "Value must share the same type.");

            if (Convert.ToUInt64(valueToTest) == 0)
                throw new ArgumentOutOfRangeException("valueToTest",
                    "Value must not be 0");

            return (Convert.ToUInt64(enumValue) & Convert.ToUInt64(valueToTest))
                == Convert.ToUInt64(valueToTest);
        }

        /// <summary>  
        /// Test if the value is clear with this flag.  
        /// </summary>  
        public static Boolean IsClear(this Enum enumValue,
            Enum valueToTest)
        {
            if (enumValue.GetType() != valueToTest.GetType())
                throw new ArgumentException("valueToTest",
                    "Value must share the same type.");

            if (Convert.ToUInt64(valueToTest) == 0)
                throw new ArgumentOutOfRangeException("valueToTest",
                    "Value must not be 0");

            return !IsSet(enumValue, valueToTest);
        }

        /// <summary>  
        /// Test if one of these test flags is set to this value.  
        /// </summary>  
        public static Boolean AnyFlagsSet(this Enum enumValue,
            Enum testValues)
        {
            if (enumValue.GetType() != testValues.GetType())
                throw new ArgumentException("testValues",
                    "Value must share the same type.");

            return ((Convert.ToUInt64(enumValue) & Convert.ToUInt64(testValues))
                != 0);
        }

        /// <summary>  
        /// Return a new value that set with specific flags.  
        /// </summary>  
        public static TEnum Set<TEnum>(this Enum enumValue, TEnum setValues)
        {
            if (enumValue.GetType() != setValues.GetType())
                throw new ArgumentException("setValues",
                    "Value must share the same type.");

            return (TEnum)Enum.ToObject(enumValue.GetType(),
                Convert.ToUInt64(enumValue) | Convert.ToUInt64(setValues));
        }

        /// <summary>  
        /// Return a new value with specific flags removed from this value.  
        /// </summary>  
        public static TEnum Clear<TEnum>(this Enum enumValue, TEnum clearValues)
        {
            if (enumValue.GetType() != clearValues.GetType())
                throw new ArgumentException("clearValues",
                    "Value must share the same type.");

            return (TEnum)Enum.ToObject(enumValue.GetType(),
                Convert.ToUInt64(enumValue) & ~Convert.ToUInt64(clearValues));
        }

        /// <summary>  
        /// For each flag in the value, perform the specific action.  
        /// </summary>  
        public static void ForEach<TEnum>(
            this Enum enumValue, Action<TEnum> processValue)
        {
            if (processValue == null)
                throw new ArgumentNullException("processValue");

            if (enumValue.GetType() != typeof(TEnum))
                throw new ArgumentException("type:TEnum",
                    "processValue's parameter must have the same type.");

            for (UInt64 bit = 1; bit != 0; bit <<= 1)
            {
                UInt64 temp = Convert.ToUInt64(enumValue) & bit;
                if (temp != 0)
                    processValue((TEnum)Enum.ToObject(typeof(TEnum), temp));
            }
        }
    }
}
